from cmath import inf
from typing import List, Dict, Tuple
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtCharts import *
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from agh_data import AGHData
from agh_obj import Flight, Gate, VehicleState, Vehicle, Park, VehicleType, VEHICLE_NAMES
from color_palette import *
import toolbox

CLOCK_HEIGHT = 64
CLOCK_WIDTH = 128

ZOOM_FACTOR = 1.2
VEHICLE_WITDH = 48
VEHICLE_HEIGHT = 16
POINT_RADIUS = 8

VEHICLE_NAME_X = POINT_RADIUS * 2 ** 0.5 - POINT_RADIUS
VEHICLE_NAME_Y = VEHICLE_NAME_X

ROAD_Z = 0
VEHICLE_Z = 1
PARK_AND_GATE_Z = 2
TIP_Z = 3 

TIP_SPACEING = 10

SCENE_MARGIN = 100

class SignalCenter(QObject):
    show_tip_signal = pyqtSignal(object)
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance =  super().__new__(cls)
        return cls._instance

class BaseGraphItem(QGraphicsItemGroup):
    _signal_center = SignalCenter()
    def __init__(self, object:object) -> None:
        super().__init__()
        self.object = object

    def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        BaseGraphItem._signal_center.show_tip_signal.emit(self.object)

class BaseTipWidget(QGraphicsWidget):
    def __init__(self) -> None:
        super().__init__()
        self.content_proxy = QGraphicsProxyWidget()
        self.content_proxy.setWidget(self.get_content_widget())
        self.content_proxy.setGraphicsEffect(toolbox.get_shadow_effect())
        self.content_proxy.setContentsMargins(4, 4, 4, 4)
        self.content_proxy.widget().setStyleSheet("background-color:#88ffffff")

        layout = QGraphicsLinearLayout(Qt.Orientation.Vertical)
        layout.addItem(self.content_proxy)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        self.setVisible(False)

    def set_arrow_pos(self, point:QPointF):
        content_size = self.content_proxy.size()
        x = point.x() - content_size.width() / 2
        y = point.y() - content_size.height() - TIP_SPACEING
        super().setPos(x, y)

    def change_visible_state(self):
        self.setVisible(not self.isVisible())

    def get_content_widget(self) -> QWidget:
        return 

    def update_content(self):
        return 

class GateTipWidget(BaseTipWidget):
    def __init__(self, gate:Gate) -> None:
        self.gate = gate
        super().__init__()
        
    def get_content_widget(self) -> QWidget:
        content = QWidget()
        font = QFont()
        font.setPointSize(6)
        title_label = QLabel(self.gate.name, content)
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        title_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        title_label.setFont(font)

        self.vehilce_labels:List[QLabel] = []
        self.progress_widgets:List[toolbox.CircleProgeressWidget] = []

        for name in VEHICLE_NAMES:
            label = QLabel(name, content)
            label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground) 
            label.setFont(font)
            self.vehilce_labels.append(label)

            progress_widget = toolbox.CircleProgeressWidget(6)
            progress_widget.setParent(content)
            self.progress_widgets.append(progress_widget)
            
        layout = QGridLayout()
        layout.addWidget(title_label, 0, 0, 1, 4)

        self.flight_label = QLabel()
        self.flight_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        self.flight_label.setFont(font)
        self.flight_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
        layout.addWidget(self.flight_label, 1, 0, 1, 4, Qt.AlignmentFlag.AlignLeft)

        row , col = 2, 2
        for i in range(len(VEHICLE_NAMES)):
            layout.addWidget(self.vehilce_labels[i], i // row + 2 , i % row % col * 2, Qt.AlignmentFlag.AlignVCenter)
            layout.addWidget(self.progress_widgets[i], i // row + 2, i % row % col * 2 + 1, Qt.AlignmentFlag.AlignCenter)

        layout.setContentsMargins(4, 4, 4, 4)
        layout.setSpacing(4)
        content.setLayout(layout)
        return content

    def update_content(self):
        for i, v in enumerate(self.gate.vehicles):
            progress_widget = self.progress_widgets[i]
            vehicle_label = self.vehilce_labels[i]
            if self.gate.is_near_gate and i == VehicleType.ShuttleBus.value:
                vehicle_label.setText(f"{VEHICLE_NAMES[i]}")
                progress_widget.update_content(DONT_NEED_VEHICLE_COLOR, 1)
                continue

            if not isinstance(v, Vehicle):
                vehicle_label.setText(f"{VEHICLE_NAMES[i]}")
                if self.gate.vehicles_work_done[i]:
                    progress_widget.update_content(VEHICLE_COMPLETE_COLOR, 1)
                else:
                    progress_widget.update_content(NO_VEHICLE_COLOR, 1)
                continue

            vehicle_label.setText(f"{VEHICLE_NAMES[i]}{v.index}")

            vehicle_state = v.state
            progress_value = v.get_progress_value()
            if vehicle_state == VehicleState.Coming:
                progress_widget.update_content(VEHICLE_COMING_COLOR, progress_value)
            elif vehicle_state == VehicleState.Waiting:
                progress_widget.update_content(VEHICLE_WAITING_COLOR, 1)
            elif vehicle_state == VehicleState.Serving:
                progress_widget.update_content(VEHICLE_COMPLETE_COLOR, progress_value)
            elif vehicle_state == VehicleState.Complete:
                progress_widget.update_content(VEHICLE_COMPLETE_COLOR, 1)


        if isinstance(flight:=self.gate.flight, Flight):
            self.flight_label.setText(f"航班：{flight.name} {flight.arrival_time[-5:]}")
        else:
            self.flight_label.setText("航班：暂无")  

class ParkTipWidget(BaseTipWidget):
    def __init__(self, park:Park) -> None:
        self.park = park
        super().__init__()

    def get_content_widget(self) -> QWidget:
        content = QWidget()
        font = QFont()
        font.setPointSize(6)
        self.title_label = QLabel(self.park.name, content)
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        self.title_label.setFont(font)

        self.vehicles_label = QLabel(content)
        self.vehicles_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.vehicles_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        self.vehicles_label.setFont(font)
        #在这里调用是为了提前确定vehicles_label的大小，以便set_arrow_pos可以到正确的位置
        self.update_content()

        layout = QGridLayout()
        layout.addWidget(self.title_label, 0, 0)
        layout.addWidget(self.vehicles_label, 1, 0)
        layout.setContentsMargins(4, 4, 4, 4)
        layout.setSpacing(4)
        content.setLayout(layout)
        return content

    def update_content(self):
        table = [[]]
        flag = 0
        for type in VehicleType:
            if len(vd:=self.park.vehicles[type.value]) != 0:
                in_park_num = len(list(filter(lambda v: v.state == VehicleState.Standby or v.state == VehicleState.Recovering, vd)))
                table[-1].append(f"{VEHICLE_NAMES[type.value]}：{in_park_num}/{len(vd)}")
                flag += 1
                if flag % 2 == 0:
                    table.append([])
        self.vehicles_label.setText(toolbox.get_html_table(table, cellpadding=2, align="left"))

class VehicleTipWidget(BaseTipWidget):
    def __init__(self, vehicle:Vehicle) -> None:
        self.vehicle = vehicle
        super().__init__()

    def get_content_widget(self) -> QWidget:
        content = QWidget()
        font = QFont()
        font.setPointSize(6)
        self.title_label = QLabel(f"{VEHICLE_NAMES[self.vehicle.type.value]}{self.vehicle.index}", content)
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        self.title_label.setFont(font)

        self.vehicle_property_label = QLabel(content)
        self.vehicle_property_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
        self.vehicle_property_label.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
        self.vehicle_property_label.setFont(font)
        #在这里调用是为了提前确定vehicles_label的大小，以便set_arrow_pos可以到正确的位置
        self.update_content()

        layout = QGridLayout()
        layout.addWidget(self.title_label, 0, 0)
        layout.addWidget(self.vehicle_property_label, 1, 0)
        layout.setContentsMargins(4, 4, 4, 4)
        layout.setSpacing(4)
        content.setLayout(layout)
        return content
    
    def update_content(self):
        info = f"速度：{self.vehicle.speed}"
        departure, destination = self.vehicle.path.description.split("->")
        info += '\n' +f"出发地：{departure}"
        info += '\n' + f"目的地：{destination}"
        info += '\n' + f"行驶进度：{int(self.vehicle.get_progress_value() * 100)}%"    
        self.vehicle_property_label.setText(info)

class GateGraphItem(BaseGraphItem):
    def __init__(self, gate:Gate) -> None:
        super().__init__(gate)

        text = QGraphicsTextItem(gate.name)
        point = QGraphicsEllipseItem(QRectF(-POINT_RADIUS, -POINT_RADIUS, 2 * POINT_RADIUS, 2 * POINT_RADIUS))
        point.setBrush(GATE_ITEM_COLOR)
        point.setPen(GATE_ITEM_COLOR)

        self.addToGroup(point)
        self.addToGroup(text)
    
class ParkGraphItem(BaseGraphItem):
    def __init__(self, park:Park) -> None:
        super().__init__(park)
        text = QGraphicsTextItem(park.name)
        point = QGraphicsEllipseItem(QRectF(-POINT_RADIUS, -POINT_RADIUS, 2 * POINT_RADIUS, 2 * POINT_RADIUS))
        point.setBrush(PARK_ITEM_COLOR)
        point.setPen(PARK_ITEM_COLOR)
        self.addToGroup(point)
        self.addToGroup(text)

class VehicleGraphItem(BaseGraphItem):
    def __init__(self, vehicle:Vehicle) -> None:
        super().__init__(vehicle)
        self.point = QGraphicsEllipseItem(QRectF(-POINT_RADIUS, -POINT_RADIUS, 2 * POINT_RADIUS, 2 * POINT_RADIUS))
        self.point.setBrush(VEHICLE_ITEM_COLOR)
        self.point.setPen(VEHICLE_ITEM_COLOR)
        self.addToGroup(self.point)

        self.arrow = QGraphicsSvgItem('icon/v_arrow.svg')
        self.arrow.setPos(-POINT_RADIUS, -POINT_RADIUS)
        self.arrow.setTransformOriginPoint(POINT_RADIUS, POINT_RADIUS)
        self.addToGroup(self.arrow)

        font = QFont()
        font.setPointSize(6)
        label = QLabel(f"{VEHICLE_NAMES[vehicle.type.value]}{vehicle.index}")
        label.setFont(font)
        label.setMargin(2)
        label.setStyleSheet("background-color:#88ffffff")

        proxy = QGraphicsProxyWidget()
        proxy.setWidget(label)
        proxy.setPos(VEHICLE_NAME_X , VEHICLE_NAME_Y)
        proxy.setZValue(-1)
        self.addToGroup(proxy)
    
    def setRotation(self, angle: float) -> None:
        self.arrow.setRotation(angle)

class GraphWidget(QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.gate_items:List[GateGraphItem] = []
        self.park_items:List[ParkGraphItem] = []
        self.vehicle_items:List[List[VehicleGraphItem]] = []
        self.line_items:List[QGraphicsLineItem] = []
        self.tip_widgets:Dict[int, BaseTipWidget] = {}
        self.can_show_vehicle_tip = False
        
        scene = QGraphicsScene()
        scene.setItemIndexMethod(QGraphicsScene.ItemIndexMethod.NoIndex)
        scene.setBackgroundBrush(SCENE_BG_COLOR)
        self.scene:QGraphicsScene = scene

        view = QGraphicsView()
        view.setScene(scene)
        view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        view.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
        view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
        view.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.TextAntialiasing)
        view.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate)
        view.horizontalScrollBar().installEventFilter(self)
        view.verticalScrollBar().installEventFilter(self)
        self.view = view

        self.info_layer = self.get_info_layer()

        layout = QStackedLayout()
        layout.addWidget(self.view)
        layout.addWidget(self.info_layer)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setStackingMode(QStackedLayout.StackingMode.StackAll)
        self.setLayout(layout)
        self.update_flight_list([])
        self.update_operation_list([])
        self.update_cost_label(0, 0)

        SignalCenter().show_tip_signal.connect(self.tip_signal_slot)

    def tip_signal_slot(self, object):
        if id(object) not in self.tip_widgets:
            if isinstance(object, Gate):
                tip_item = GateTipWidget(object)
                pos = self.gate_items[object.index].pos()
            elif isinstance(object, Park):
                tip_item = ParkTipWidget(object)
                pos = self.park_items[object.index].pos()
            elif isinstance(object, Vehicle):
                if not self.can_show_vehicle_tip:
                    return
                tip_item = VehicleTipWidget(object)
                pos = self.vehicle_items[object.type.value][object.index].pos()

            tip_item.set_arrow_pos(pos)
            tip_item.setZValue(TIP_Z)
            self.tip_widgets[id(object)] = tip_item
            self.scene.addItem(tip_item)
            
        tip_item = self.tip_widgets[id(object)]

        if isinstance(object, Vehicle):
            (x, y), _ = object.path.get_point_of_progress(object.get_progress_value())
            tip_item.set_arrow_pos(QPointF(x, y))

        tip_item.update_content()
        tip_item.change_visible_state()

    def get_info_layer(self) -> QWidget:
        content = QWidget()
        
        self.clock_widget = toolbox.ClockWidget()
        self.clock_widget.clock.setFixedWidth(CLOCK_WIDTH)
        self.clock_widget.setFixedSize(CLOCK_WIDTH, CLOCK_HEIGHT)
        self.clock_widget.setStyleSheet("background-color:#eeffffff")
        self.clock_widget.setGraphicsEffect(toolbox.get_shadow_effect())

        self.op_label = QLabel("")
        self.op_label.setMargin(4)
        self.op_label.setStyleSheet("background-color:#eeffffff")
        self.op_label.setGraphicsEffect(toolbox.get_shadow_effect())
        
        self.flight_label = QLabel("")
        self.flight_label.setMargin(4)
        self.flight_label.setStyleSheet("background-color:#eeffffff")
        self.flight_label.setGraphicsEffect(toolbox.get_shadow_effect())

        self.cost_label = QLabel("")
        self.cost_label.setMargin(4)
        self.cost_label.setStyleSheet("background-color:#eeffffff")
        self.cost_label.setGraphicsEffect(toolbox.get_shadow_effect())

        layout = QGridLayout()
        layout.addWidget(self.flight_label, 0, 0, Qt.AlignmentFlag.AlignTop|Qt.AlignmentFlag.AlignLeft)
        layout.addItem(toolbox.get_spacer_item(expand_v = False), 0, 1)
        layout.addWidget(self.op_label, 0, 2, Qt.AlignmentFlag.AlignTop|Qt.AlignmentFlag.AlignRight)
        layout.addItem(toolbox.get_spacer_item(expand_h = False), 1, 0, columnSpan = 3)
        layout.addWidget(self.cost_label, 2, 0, Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignLeft)
        layout.addItem(toolbox.get_spacer_item(expand_v = False), 2, 1)
        layout.addWidget(self.clock_widget, 2, 2, Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight)
        content.setLayout(layout)
        content.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
        return content

    def update_cost_label(self, total_cost:float, avg_cost:float):
        self.cost_label.setText(toolbox.get_html_table([[f'{total_cost:.2f}', f'{avg_cost:.2f}']], ["总代价", "平均代价"]))

    def update_flight_list(self, flight_list:List[Tuple[str, str, str]]):
        self.flight_label.setText(toolbox.get_html_table(flight_list, ["航班号", "停机位", "进港时间"]))

    def update_operation_list(self, operation_list:List[Tuple[str, str, str]]):        
        self.op_label.setText(toolbox.get_html_table(operation_list, ["车型和编号", "出发时间", "路线"]))

    def load_map(self, lines, parks:List[Park], gates:List[Gate], vehicles:List[List[Vehicle]]):
        self.clear_map()
        min_x = min_y = inf
        max_x = max_y = -inf

        pen = QPen()
        pen.setColor(ROAD_COLOR)
        pen.setWidth(4)
        for first_point, seconde_point in lines:
            min_x = min(min_x, first_point[0], seconde_point[0])
            min_y = min(min_y, first_point[1], seconde_point[1])
            max_x = max(max_x, first_point[0], seconde_point[0])
            max_y = max(max_y, first_point[1], seconde_point[1])
            line = self.scene.addLine(first_point[0], first_point[1], seconde_point[0], seconde_point[1], pen)
            line.setZValue(ROAD_Z)

        x = min_x - SCENE_MARGIN
        y = min_y - SCENE_MARGIN
        width = max_x - min_x + 2 * SCENE_MARGIN
        height = max_y - min_y + 2 * SCENE_MARGIN
        self.scene.setSceneRect(x, y, width, height)
        self.view.fitInView(self.scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)

        self.gate_items = [GateGraphItem(gate) for gate in gates]
        self.park_items = [ParkGraphItem(park) for park in parks]
        self.vehicle_items = [[VehicleGraphItem(v) for v in vs] for vs in vehicles]

        for p in self.park_items:
            self.scene.addItem(p)
            x, y = p.object.pos
            p.setPos(x, y)
            p.setZValue(PARK_AND_GATE_Z)

        for g in self.gate_items:
            self.scene.addItem(g)
            x, y = g.object.pos
            g.setPos(x, y)
            g.setZValue(PARK_AND_GATE_Z)
        
        for v_items in self.vehicle_items:
            for item in v_items:
                self.scene.addItem(item)
                item.setZValue(VEHICLE_Z)
                item.setVisible(False)
    
    def clear_map(self):
        for item in self.gate_items:
            self.scene.removeItem(item)
        
        for item in self.park_items:
            self.scene.removeItem(item)
        
        for item in self.line_items:
            self.scene.removeItem(item)

        for vs in self.vehicle_items:
            for item in vs:
                self.scene.removeItem(item)
        
        self.tip_widgets.clear()
    
    def clear(self):
        for vs in self.vehicle_items:
            for item in vs:
                item.setVisible(False)
        
        for tip_widget in self.tip_widgets.values():
            if tip_widget.isVisible():
                tip_widget.setVisible(False)
        
        self.update_cost_label(0, 0)
        self.update_flight_list([])
        self.update_operation_list([])
    
    def update_graph_content(self):
        for v_items in self.vehicle_items:
            for item in v_items:
                vehicle = item.object
                if vehicle.state == VehicleState.Backing or vehicle.state == VehicleState.Coming:
                    (x, y), angle = vehicle.path.get_point_of_progress(vehicle.get_progress_value())
                    item.setPos(x , y)
                    item.setRotation(angle)
                    item.setVisible(True)
                else:
                    item.setVisible(False)
        
        for tip_widget in self.tip_widgets.values():
            if tip_widget.isVisible():
                tip_widget.update_content()

    def hide_vehicle_tip(self):
        for tip_widget in self.tip_widgets.values():
            if isinstance(tip_widget, VehicleTipWidget) and tip_widget.isVisible():
                tip_widget.setVisible(False)

    def wheelEvent(self, event: QWheelEvent) -> None:
        if QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier:
            if event.angleDelta().y() < 0:
                self.view.scale(1 / ZOOM_FACTOR, 1 / ZOOM_FACTOR)
            else:
                self.view.scale(ZOOM_FACTOR, ZOOM_FACTOR)
            return    
        return super().wheelEvent(event)
    
    def eventFilter(self, object: QObject, event: QEvent) -> bool:
        if isinstance(object, QScrollBar) and event.type() == QEvent.Type.Wheel:
            return True
        return super().eventFilter(object, event)